// Copyright 2022 jeff.li. and/or its affiliates.

#include <tbir/runtime/utf8_util.h>

#include <tbir/runtime/container/string.h>
#include <tbir/runtime/container/unicode.h>
#include <turbo/strings/utf8/codec.h>

namespace tbir::runtime {

    Unicode UTF8Decode(string_view input) {
        return UTF8Decode(input.data(), input.length());
    }

    Unicode UTF8Decode(const char *s_ptr, size_t len) {
        constexpr int SMALL_BUFFER_SIZE = 64;
        auto *s_u_ptr = (const unsigned char *) (s_ptr);
        if (len <= SMALL_BUFFER_SIZE) {  // small string
            char32_t buffer[SMALL_BUFFER_SIZE];
            auto size = turbo::utf8_details::GreedyTableDecoder::Convert(s_u_ptr, s_u_ptr + len, buffer);
            return Unicode(buffer, size);
        } else {
            // for avoid allocate memory
            auto unit_size = turbo::utf8_details::GreedyTableDecoder::CountUnitSize(s_u_ptr, s_u_ptr + len);
            Unicode::ContainerType unicodes(unit_size, Unicode::ContainerType::NoInit{});
            auto size = turbo::utf8_details::GreedyTableDecoder::Convert(s_u_ptr, s_u_ptr + len, unicodes.data());
            assert(size == unit_size);
            return Unicode(std::move(unicodes));
        }
    }

    String UTF8Encode(unicode_view input) {
        return UTF8Encode(input.data(), input.length());
    }

    String UTF8Encode(const uint32_t *s, size_t len) {
        auto bytes_size = turbo::utf8_details::GreedyCountBytesSize(s, s + len);
        String::ContainerType bytes(bytes_size, String::ContainerType::NoInit{});
        auto size = turbo::utf8_details::GreedyEncoder(s, s + len, (unsigned char *) (bytes.data()));
        assert(size == bytes_size);
        return String(std::move(bytes));
    }

    String UTF8Encode(const char32_t *s, size_t len) {
        return UTF8Encode(reinterpret_cast<const uint32_t *>(s), len);
    }

    size_t UTF8CharCounts(string_view str) noexcept {
        auto *s_u_ptr = (const unsigned char *) (str.data());
        return turbo::utf8_details::GreedyTableDecoder::CountUnitSize(s_u_ptr, s_u_ptr + str.size());
    }

    String UTF8DoLower(string_view input) {
        auto codepoints_raw = UTF8Decode(input);
        auto codepoints_lower = py_unicode_do_lower(codepoints_raw);
        return UTF8Encode(codepoints_lower);
    }

    String UTF8DoUpper(string_view input) {
        auto codepoints_raw = UTF8Decode(input);
        auto codepoints_lower = py_unicode_do_upper(codepoints_raw);
        return UTF8Encode(codepoints_lower);
    }

    bool UTF8IsDigit(string_view input) {
        auto codepoints = UTF8Decode(input);
        return py_unicode_isdigit(codepoints);
    }

    bool UTF8IsAlpha(string_view input) {
        auto codepoints = UTF8Decode(input);
        return py_unicode_isalpha(codepoints);
    }

    String AsciiDoLower(string_view sv) {
        String::ContainerType buffer(sv.size(), String::ContainerType::NoInit{});
        auto *s_ptr = buffer.data();
        for (auto it = sv.begin(); it < sv.end(); ++it) {
            char c = *it;
            if (c <= 'Z' && c >= 'A') {
                *s_ptr = static_cast<char>(c - 'A' + 'a');
            } else {
                *s_ptr = c;
            }
            ++s_ptr;
        }
        return String(std::move(buffer));
    }

    String AsciiDoUpper(string_view sv) {
        String::ContainerType buffer(sv.size(), String::ContainerType::NoInit{});
        auto *s_ptr = buffer.data();
        for (auto it = sv.begin(); it < sv.end(); ++it) {
            auto c = *it;
            if (c <= 'z' && c >= 'a') {
                *s_ptr = static_cast<char>(c - 'a' + 'A');
            } else {
                *s_ptr = c;
            }
            ++s_ptr;
        }
        return String(std::move(buffer));
    }

}  // namespace tbir::runtime
